/**
 * // Copyright (c) 2023 KylinOS. All rights reserved.
 * //
 * // This program is free software; you can redistribute it
 * // and/or modify it under the terms of the GNU General Public
 * // License (version 2) as published by the FSF - Free Software
 * // Foundation*
 */

/**
 * // Copyright (C) 2023 ben LG <guoliben@kylinos.cn>
 */

package compressextractor

import (
	"archive/tar"
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"runtime"
	"strings"
	"time"

	"gitee.com/guoliben/kydlp-doctotext/3rdparty/fileidentify"
	"gitee.com/guoliben/kydlp-doctotext/compressextractor/fileattrfilter"
	"gitee.com/guoliben/kydlp-doctotext/docextractor"
	"gitee.com/guoliben/kydlp-doctotext/extractor_interface"
	"gitee.com/guoliben/kydlp-doctotext/imageextractor"
	"gitee.com/guoliben/kydlp-doctotext/utils/ctrlledprint"
)

type CompressTar struct{}

func (extractor CompressTar) GetTextFromFile(path string, size int64, filetype string, depth int) ([]byte, string) {
	ctrlledprint.Println("提取文本: 正在解析tar(file)文档...")

	return tarGetTextFromFile(path, size, filetype, depth)
}

func (extractor CompressTar) GetTextFromBuffer(path string, data []byte, size int64, filetype string, depth int) ([]byte, string) {
	ctrlledprint.Println("提取文本: 正在解析tar(buffer)文档...")

	return tarGetTextFromBuffer(path, data, size, filetype, depth)
}

func (extractor CompressTar) GetStringTextFromFile(path string, size int64, filetype string, depth int) (string, string) {
	ctrlledprint.Println("提取文本: 正在解析tar(file)文档...")

	return tarGetStringTextFromFile(path, size, filetype, depth)
}

func (extractor CompressTar) GetStringTextFromBuffer(path string, data []byte, size int64, filetype string, depth int) (string, string) {
	ctrlledprint.Println("提取文本: 正在解析tar(buffer)文档...")

	return tarGetStringTextFromBuffer(path, data, size, filetype, depth)
}

func (extractor CompressTar) GetTextFromFileWithCallback(path string, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	ctrlledprint.Println("提取文本: 正在解析tar(file)文档...")

	return tarGetTextFromFileWithCallback(path, size, filetype, depth, callback, context)
}

func (extractor CompressTar) GetTextFromBufferWithCallback(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	ctrlledprint.Println("提取文本: 正在解析tar(buffer)文档...")

	return tarGetTextFromBufferWithCallback(path, data, size, filetype, depth, callback, context)
}

func (extractor CompressTar) GetStringTextFromFileWithCallback(path string, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	ctrlledprint.Println("提取文本: 正在解析tar(file)文档...")

	return tarGetStringTextFromFileWithCallback(path, size, filetype, depth, callback, context)
}

func (extractor CompressTar) GetStringTextFromBufferWithCallback(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	ctrlledprint.Println("提取文本: 正在解析tar(buffer)文档...")

	return tarGetStringTextFromBufferWithCallback(path, data, size, filetype, depth, callback, context)
}

func tarGetTextFromFile(path string, size int64, filetype string, depth int) ([]byte, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析tar文档，达到最大解压层数:", maxDetectDepth)
		return nil, ""
	}

	start := time.Now()

	file, err := os.Open(path)
	if err != nil {
		ctrlledprint.Println("Error opening tar file:", err)
		return nil, ""
	}
	defer file.Close()

	var text bytes.Buffer

	reader := tar.NewReader(file)

	for {
		header, err := reader.Next()
		if err != nil {
			if err == io.EOF {
				ctrlledprint.Println("[tar] End reading sub file:", err)
			} else {
				ctrlledprint.Println("[tar] Error reading sub file:", err)
			}
			reader = nil
			runtime.GC()
			break
		}
		if header.Format == tar.FormatPAX {
			ctrlledprint.Println("[tar] Ignore the pax sub file!")
			continue
		}
		if header.Typeflag == tar.TypeReg {
			content, err := ioutil.ReadAll(reader)
			if err != nil {
				ctrlledprint.Println("[tar] Error reading content of sub file:", err)
				break
			}
			length := int64(len(content))

			// 调用指定类型文件解析器解析文件内容
			// -------------------------------------------------------------------------------------------------------
			filetype, _, _, _ := fileidentify.IdentifyFromBuffer(content)

			switch strings.TrimSpace(filetype) {
			case fileidentify.DocTypeText, fileidentify.DocTypeHtml, fileidentify.DocTypeXml, fileidentify.DocTypeJson,
				fileidentify.DocTypePdf,
				fileidentify.DocTypeMsDoc, fileidentify.DocTypeMsDocx,
				fileidentify.DocTypeMsXls, fileidentify.DocTypeMsXlsx,
				fileidentify.DocTypeMsPpt, fileidentify.DocTypeMsPptx:
				{
					if result, _ := docextractor.TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
						text.Write(result)
					}
				}
			case fileidentify.CompressTypeTar, fileidentify.CompressTypeGzOrTarGz, fileidentify.CompressTypeZip,
				fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2,
				fileidentify.CompressType7z:
				{
					if result, _ := TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth + 1); len(result) > 0 {
						text.Write(result)
					}
				}
			case fileidentify.ImageTypeBmp, fileidentify.ImageTypeJpeg, fileidentify.ImageTypePng,
				fileidentify.ImageTypeGif, fileidentify.ImageTypeTiff:
				{
					if result, _ := imageextractor.TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
						text.Write(result)
					}
				}
			}
			// -------------------------------------------------------------------------------------------------------
		}

	}

	elapsed := time.Since(start)

	return text.Bytes(), elapsed.String()
}

func tarGetTextFromBuffer(path string, data []byte, size int64, filetype string, depth int) ([]byte, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析tar文档，达到最大解压层数:", maxDetectDepth)
		return nil, ""
	}

	start := time.Now()

	var text bytes.Buffer

	reader := tar.NewReader(bytes.NewReader(data))

	for {
		header, err := reader.Next()
		if err != nil {
			if err == io.EOF {
				ctrlledprint.Println("[tar] End reading sub file:", err)
			} else {
				ctrlledprint.Println("[tar] Error reading sub file:", err)
			}
			reader = nil
			runtime.GC()
			break
		}
		if header.Format == tar.FormatPAX {
			ctrlledprint.Println("[tar] Ignore the pax sub file!")
			continue
		}
		if header.Typeflag == tar.TypeReg {
			content, err := ioutil.ReadAll(reader)
			if err != nil {
				ctrlledprint.Println("[tar] Error reading content of sub file:", err)
				break
			}
			length := int64(len(content))

			// 调用指定类型文件解析器解析文件内容
			// -------------------------------------------------------------------------------------------------------
			filetype, _, _, _ := fileidentify.IdentifyFromBuffer(content)

			switch strings.TrimSpace(filetype) {
			case fileidentify.DocTypeText, fileidentify.DocTypeHtml, fileidentify.DocTypeXml, fileidentify.DocTypeJson,
				fileidentify.DocTypePdf,
				fileidentify.DocTypeMsDoc, fileidentify.DocTypeMsDocx,
				fileidentify.DocTypeMsXls, fileidentify.DocTypeMsXlsx,
				fileidentify.DocTypeMsPpt, fileidentify.DocTypeMsPptx:
				{
					if result, _ := docextractor.TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
						text.Write(result)
					}
				}
			case fileidentify.CompressTypeTar, fileidentify.CompressTypeGzOrTarGz, fileidentify.CompressTypeZip,
				fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2,
				fileidentify.CompressType7z:
				{
					if result, _ := TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth + 1); len(result) > 0 {
						text.Write(result)
					}
				}
			case fileidentify.ImageTypeBmp, fileidentify.ImageTypeJpeg, fileidentify.ImageTypePng,
				fileidentify.ImageTypeGif, fileidentify.ImageTypeTiff:
				{
					if result, _ := imageextractor.TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
						text.Write(result)
					}
				}
			}
			// -------------------------------------------------------------------------------------------------------
		}

	}

	elapsed := time.Since(start)

	return text.Bytes(), elapsed.String()
}

func tarGetStringTextFromFile(path string, size int64, filetype string, depth int) (string, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析tar文档，达到最大解压层数:", maxDetectDepth)
		return "", ""
	}

	start := time.Now()

	file, err := os.Open(path)
	if err != nil {
		ctrlledprint.Println("Error opening tar file:", err)
		return "", ""
	}
	defer file.Close()

	var text bytes.Buffer

	reader := tar.NewReader(file)

	for {
		header, err := reader.Next()
		if err != nil {
			if err == io.EOF {
				ctrlledprint.Println("[tar] End reading sub file:", err)
			} else {
				ctrlledprint.Println("[tar] Error reading sub file:", err)
			}
			reader = nil
			runtime.GC()
			break
		}
		if header.Format == tar.FormatPAX {
			ctrlledprint.Println("[tar] Ignore the pax sub file!")
			continue
		}
		if header.Typeflag == tar.TypeReg {
			content, err := ioutil.ReadAll(reader)
			if err != nil {
				ctrlledprint.Println("[tar] Error reading content of sub file:", err)
				break
			}
			length := int64(len(content))

			// 调用指定类型文件解析器解析文件内容
			// -------------------------------------------------------------------------------------------------------
			filetype, _, _, _ := fileidentify.IdentifyFromBuffer(content)

			switch strings.TrimSpace(filetype) {
			case fileidentify.DocTypeText, fileidentify.DocTypeHtml, fileidentify.DocTypeXml, fileidentify.DocTypeJson,
				fileidentify.DocTypePdf,
				fileidentify.DocTypeMsDoc, fileidentify.DocTypeMsDocx,
				fileidentify.DocTypeMsXls, fileidentify.DocTypeMsXlsx,
				fileidentify.DocTypeMsPpt, fileidentify.DocTypeMsPptx:
				{
					if result, _ := docextractor.TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
						text.WriteString(result)
					}
				}
			case fileidentify.CompressTypeTar, fileidentify.CompressTypeGzOrTarGz, fileidentify.CompressTypeZip,
				fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2,
				fileidentify.CompressType7z:
				{
					if result, _ := TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth + 1); len(result) > 0 {
						text.WriteString(result)
					}
				}
			case fileidentify.ImageTypeBmp, fileidentify.ImageTypeJpeg, fileidentify.ImageTypePng,
				fileidentify.ImageTypeGif, fileidentify.ImageTypeTiff:
				{
					if result, _ := imageextractor.TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
						text.WriteString(result)
					}
				}
			}
			// -------------------------------------------------------------------------------------------------------
		}

	}

	elapsed := time.Since(start)

	return text.String(), elapsed.String()
}

func tarGetStringTextFromBuffer(path string, data []byte, size int64, filetype string, depth int) (string, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析tar文档，达到最大解压层数:", maxDetectDepth)
		return "", ""
	}

	start := time.Now()

	var text bytes.Buffer

	reader := tar.NewReader(bytes.NewReader(data))

	for {
		header, err := reader.Next()
		if err != nil {
			if err == io.EOF {
				ctrlledprint.Println("[tar] End reading sub file:", err)
			} else {
				ctrlledprint.Println("[tar] Error reading sub file:", err)
			}
			reader = nil
			runtime.GC()
			break
		}
		if header.Format == tar.FormatPAX {
			ctrlledprint.Println("[tar] Ignore the pax sub file!")
			continue
		}
		if header.Typeflag == tar.TypeReg {
			content, err := ioutil.ReadAll(reader)
			if err != nil {
				ctrlledprint.Println("[tar] Error reading content of sub file:", err)
				break
			}
			length := int64(len(content))

			// 调用指定类型文件解析器解析文件内容
			// -------------------------------------------------------------------------------------------------------
			filetype, _, _, _ := fileidentify.IdentifyFromBuffer(content)

			switch strings.TrimSpace(filetype) {
			case fileidentify.DocTypeText, fileidentify.DocTypeHtml, fileidentify.DocTypeXml, fileidentify.DocTypeJson,
				fileidentify.DocTypePdf,
				fileidentify.DocTypeMsDoc, fileidentify.DocTypeMsDocx,
				fileidentify.DocTypeMsXls, fileidentify.DocTypeMsXlsx,
				fileidentify.DocTypeMsPpt, fileidentify.DocTypeMsPptx:
				{
					if result, _ := docextractor.TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
						text.WriteString(result)
					}
				}
			case fileidentify.CompressTypeTar, fileidentify.CompressTypeGzOrTarGz, fileidentify.CompressTypeZip,
				fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2,
				fileidentify.CompressType7z:
				{
					if result, _ := TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth + 1); len(result) > 0 {
						text.WriteString(result)
					}
				}
			case fileidentify.ImageTypeBmp, fileidentify.ImageTypeJpeg, fileidentify.ImageTypePng,
				fileidentify.ImageTypeGif, fileidentify.ImageTypeTiff:
				{
					if result, _ := imageextractor.TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
						text.WriteString(result)
					}
				}
			}
			// -------------------------------------------------------------------------------------------------------
		}

	}

	elapsed := time.Since(start)

	return text.String(), elapsed.String()
}

func tarGetTextFromFileWithCallback(path string, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析tar文档，达到最大解压层数:", maxDetectDepth)
		return 0, nil, ""
	}

	file, err := os.Open(path)
	if err != nil {
		ctrlledprint.Println("Error opening tar file:", err)
		return 0, nil, ""
	}
	defer file.Close()

	reader := tar.NewReader(file)

	defer func() {
		reader = nil
	}()

	return tarGetText(path, reader, nil, size, filetype, depth, callback, context)
}

func tarGetTextFromBufferWithCallback(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析tar文档，达到最大解压层数:", maxDetectDepth)
		return 0, nil, ""
	}

	reader := tar.NewReader(bytes.NewReader(data))

	return tarGetText(path, reader, nil, size, filetype, depth, callback, context)
}

func tarGetStringTextFromFileWithCallback(path string, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析tar文档，达到最大解压层数:", maxDetectDepth)
		return 0, "", ""
	}

	file, err := os.Open(path)
	if err != nil {
		ctrlledprint.Println("Error opening tar file:", err)
		return 0, "", ""
	}
	defer file.Close()

	reader := tar.NewReader(file)

	defer func() {
		reader = nil
	}()

	return tarGetStringText(path, reader, nil, size, filetype, depth, callback, context)
}

func tarGetStringTextFromBufferWithCallback(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析tar文档，达到最大解压层数:", maxDetectDepth)
		return 0, "", ""
	}

	reader := tar.NewReader(bytes.NewReader(data))

	return tarGetStringText(path, reader, nil, size, filetype, depth, callback, context)
}

// tarGetText .tar(以及.tar.gz)格式文件统一解析入口
func tarGetText(path string, reader *tar.Reader, prevheader *tar.Header, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	var retcode int
	var totaltext bytes.Buffer
	var totalelapsed time.Duration

	start := time.Now()

	for {
		var header *tar.Header
		if prevheader != nil {
			header = prevheader
			prevheader = nil
		} else {
			var err error
			header, err = reader.Next()
			if err != nil {
				if err == io.EOF {
					ctrlledprint.Println("[tar] End reading sub file:", err)
				} else {
					ctrlledprint.Println("[tar] Error reading sub file:", err)
				}
				break
			}
		}
		if header.Format == tar.FormatPAX {
			ctrlledprint.Println("[tar] Ignore the pax sub file!")
			continue
		}
		if header.Typeflag == tar.TypeReg {
			subfilename := header.Name
			subfilesize := header.Size
			if fileattrfilter.FilterExcludeFilepath(subfilename) {
				continue
			}
			if fileattrfilter.FilterExcludeFilesize("", subfilesize) {
				continue
			}

			retcode = ExtractTextWithCallback(path, reader, depth, callback, context)

			if retcode == 0 {
				continue
			} else {
				break
			}
		}
	}

	elapsed := time.Since(start)

	totalelapsed += elapsed

	return retcode, totaltext.Bytes(), totalelapsed.String()
}

func tarGetStringText(path string, reader *tar.Reader, prevheader *tar.Header, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	var retcode int
	var totaltext bytes.Buffer
	var totalelapsed time.Duration

	start := time.Now()

	for {
		var header *tar.Header
		if prevheader != nil {
			header = prevheader
			prevheader = nil
		} else {
			var err error
			header, err = reader.Next()
			if err != nil {
				if err == io.EOF {
					ctrlledprint.Println("[tar] End reading sub file:", err)
				} else {
					ctrlledprint.Println("[tar] Error reading sub file:", err)
				}
				break
			}
		}
		if header.Format == tar.FormatPAX {
			ctrlledprint.Println("[tar] Ignore the pax sub file!")
			continue
		}
		if header.Typeflag == tar.TypeReg {
			subfilename := header.Name
			subfilesize := header.Size
			if fileattrfilter.FilterExcludeFilepath(subfilename) {
				continue
			}
			if fileattrfilter.FilterExcludeFilesize("", subfilesize) {
				continue
			}

			retcode = ExtractStringTextWithCallback(path, reader, depth, callback, context)

			if retcode == 0 {
				continue
			} else {
				break
			}
		}
	}

	elapsed := time.Since(start)

	totalelapsed += elapsed

	return retcode, totaltext.String(), totalelapsed.String()
}

// TestTarReader 检测文件格式是否为tar
func TestTarReader(reader *tar.Reader) (*tar.Header, bool) {
	tarReader := reader
	header, err := tarReader.Next()
	if err != nil {
		ctrlledprint.Println("检测文件格式是否为tar失败:", err)
		return nil, false
	}
	return header, true
}
